<?php
// +----------------------------------------------------------------------
// | 实体服务层
// +----------------------------------------------------------------------
// | 主要服务于实体数据处理
// +----------------------------------------------------------------------
// | 错误编码头 205xxx
// +----------------------------------------------------------------------
namespace common\service;

use common\exception\LogicException;
use common\model\EntityModel;
use common\model\ProjectModel;
use common\model\StatusModel;
use common\model\StepWorkflowModel;
use common\model\TaskModel;
use support\ErrorCode;
use support\SingletonTrait;
use think\Exception;
use think\module\Module;
use Throwable;
use Webman\Stomp\Client;

class EntityGenerateService
{

    /**
     * @var EntityModel
     */
    private $entityModel;
    private $taskModel;

    // 单例工具
    use SingletonTrait;

    public function __construct()
    {
        $this->entityModel = model(EntityModel::class);
        $this->taskModel = model(TaskModel::class);
    }

    /**
     * 通过概念生成资产和关卡
     * @param int $projectId
     * @param int $tenantId
     * @param array $filter
     * @param string $entityStatus
     * @param string $taskDefaultStatus
     * @param array $stepCategoryTaskDefaultValue
     * @return array
     * @throws Exception
     * @throws Throwable
     */
    public function generateAssetAndLevelByDesign(int $projectId, int $tenantId, $filter = [], string $entityStatus = 'task_generated', string $taskDefaultStatus = '', $stepCategoryTaskDefaultValue = [])
    {
        $designModuleId = Module::$moduleDictData['module_index_by_code']['design']['id'];
        $assetModuleId = Module::$moduleDictData['module_index_by_code']['asset']['id'];
        $levelModuleId = Module::$moduleDictData['module_index_by_code']['level']['id'];

        // 1. 取出所有未生成后续任务分镜 is_generated_follow = no
        $entityModel = model(EntityModel::class);

        $entityFilter = [
            'project_id' => $projectId,
            'module_id' => $designModuleId,
            'tenant_id' => $tenantId,
//            'is_generated_follow' => 'no', //@update 2021年5月13日 支持重复发布 更新信息到下游实体
        ];

        if (!empty($filter)) {
            $entityFilter['_complex'] = $filter;
        }

        $publishedStatusList = StatusService::getInstance()->getEntityPublishedStatusList('design');
        $publishedStatusIdList = array_column($publishedStatusList, 'id');
        $designStatus = StatusService::getInstance()->getOne(['code' => $entityStatus], '*');
        $notGeneratedDesignData = model(EntityModel::class)
            ->field("
            id,
            name,
            code,
            tenant_id,
            project_id,
            description,
            initial_episode_code,
            showings_number,
            is_confirm,
            director_star,
            key_shot,
            is_append,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.design_grade')) AS grade,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.design_style')) AS style,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.design_category')) AS category,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.design_perform_species')) AS perform_species,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.level_relate_asset')) AS level_relate_asset,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.design_relate_level')) AS relate_level,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.design_time_of_date')) AS time_of_date,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.design_weather')) AS weather,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.design_is_need_bind')) AS is_need_bind,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.design_special_effect_type')) AS special_effect_type,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.design_binding_attribute')) AS binding_attribute,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.design_solution_asset')) AS solution_asset,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.design_task')) AS todo_task,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.design_status')) AS status,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.design_skip_remove_rigging_control')) AS skip_remove_rigging_control,
            JSON_UNQUOTE(JSON_EXTRACT(json, '$.design_hair_making')) AS hair_making

            ")
            ->where($entityFilter)
            ->select();

        $res = [];
        if (empty($notGeneratedDesignData)) {
            return $res;
        }

        // 当前处理得概念主键ids
        $designEntityNameList = array_column($notGeneratedDesignData, 'name');

        $episodeList = []; // 所属集列表
        $assetEntityAddData = []; // 待添加资产实体数据
        $levelEntityAddData = []; // 待添加关卡实体数据

        $relateAssetDict = []; // 关联资产字典
        $relateLevelDict = []; // 资产关联关卡字典
        $relateAssetNameList = []; // 关联资产名称列表
        $relateLevelNameList = []; // 关联关卡名称列表

        // 已经生成的关卡
        $alreadyGenerateLevels = $this->entityModel
            ->where(['tenant_id' => $tenantId, 'module_id' => $levelModuleId, 'project_id' => $projectId])
            ->where(['name' => ['IN', implode(',', $designEntityNameList)]])
            ->field("id,name,code,step_workflow_id,json")
            ->select();
        $alreadyGenerateLevels = array_map(function ($item) {
            $item['json'] = json_decode($item['json'] ?? '{}', true);
            return $item;
        }, $alreadyGenerateLevels);

        $alreadyGenerateLevels = array_column($alreadyGenerateLevels, null, 'name');
        // 已经生成的资产
        $alreadyGenerateAssets = $this->entityModel
            ->where(['tenant_id' => $tenantId, 'module_id' => $assetModuleId, 'project_id' => $projectId])
            ->where(['name' => ['IN', implode(',', $designEntityNameList)]])
            ->field("id,name,step_workflow_id,code,json")
            ->select();
        $alreadyGenerateAssets = array_map(function ($item) {
            $item['json'] = json_decode($item['json'] ?? '{}', true);
            return $item;
        }, $alreadyGenerateAssets);
        $alreadyGenerateAssets = array_column($alreadyGenerateAssets, null, 'name');

        $assetPublished = [];
        $levelPublished = [];
        $workflowCodes = ['level', 'asset'];
        $workflowList = StepWorkflowService::getInstance()->getList([
            'code' => ['IN', implode(',', $workflowCodes)],
            'project_id' => ['IN', [0, $projectId]],
            'is_enable' => 'yes'
        ]);
//        $workflows = array_column($workflows, null, 'code');
        $workflows = [];
        foreach ($workflowList as $workflowItem) {
            // 默认取项目id为0的工序流配置,如果有配置项目,就取项目的工序流配置
            if (empty($workflows[$workflowItem['code']] ?? []) || $workflows[$workflowItem['code']]['project_id'] == 0) {
                $workflows[$workflowItem['code']] = $workflowItem;
            }
        }

        $assetModuleDefaultAttr = ModuleService::getInstance()->getModuleDefaultAttr('asset');
        $levelModuleDefaultAttr = ModuleService::getInstance()->getModuleDefaultAttr('level');
        foreach ($notGeneratedDesignData as $notGeneratedDesignItem) {

            // 生成到待添加资产实体数据
            if ($notGeneratedDesignItem['category'] !== '关卡') {
                if (!array_key_exists('asset', $workflows)) {
                    throw_strack_exception("asset step workflow does not exist.", ErrorCode::STEP_FLOW_DOES_NOT_EXIST);
                }

                $performSpecies = !empty($notGeneratedDesignItem['perform_species']) ? $notGeneratedDesignItem['perform_species'] : '无';

                $assetAttrData = [
                    'asset_grade' => $notGeneratedDesignItem['grade'],
                    'asset_style' => $notGeneratedDesignItem['style'],
                    'asset_category' => $notGeneratedDesignItem['category'],
                    'asset_design' => $notGeneratedDesignItem['id'],
                    'asset_perform_species' => $performSpecies,
                    'asset_time_of_date' => $notGeneratedDesignItem['time_of_date'],
                    'asset_weather' => $notGeneratedDesignItem['weather'],
                    'asset_is_need_bind' => $notGeneratedDesignItem['is_need_bind'],
                    'asset_special_effect_type' => $notGeneratedDesignItem['special_effect_type'],
                    'asset_binding_attribute' => $notGeneratedDesignItem['binding_attribute'],
                    'asset_task' => $notGeneratedDesignItem['todo_task'],
                    "asset_skip_remove_rigging_control" => $notGeneratedDesignItem['skip_remove_rigging_control'] ?? "yes",
                    'asset_solution_asset' => $notGeneratedDesignItem['solution_asset'],
                    'asset_hair_making' => $notGeneratedDesignItem['hair_making'], // 毛发制作
                ];
                $assetAttrData = EntityService::getInstance()->mergeJsonFieldWithDefaultAttr($assetAttrData, $assetModuleDefaultAttr);

                $assetEntityAddTempData = [
                    'name' => $notGeneratedDesignItem['name'],
                    'code' => $notGeneratedDesignItem['code'],
                    'tenant_id' => $notGeneratedDesignItem['tenant_id'],
                    'project_id' => $notGeneratedDesignItem['project_id'],
                    'description' => $notGeneratedDesignItem['description'],
                    'module_id' => $assetModuleId,
                    'step_workflow_id' => $workflows['asset']['id'],
                    'initial_episode_code' => $notGeneratedDesignItem['initial_episode_code'],
                    'showings_number' => $notGeneratedDesignItem['showings_number'],
                    'is_confirm' => $notGeneratedDesignItem['is_confirm'],
                    'director_star' => $notGeneratedDesignItem['director_star'],
                    'key_shot' => $notGeneratedDesignItem['key_shot'],
                    'is_append' => $notGeneratedDesignItem['is_append'],
//                        'estimate_hours' => $assetManHour,
                    //                        'cost_hours' => $assetManHour,
                    //                        'created_by' => fill_created_by(),
                    //                        'created' => time(),
                    //                        'uuid' => create_uuid(),
                    'json' => $assetAttrData,
                ];
                // 判断是否已经生成过
                if (isset($alreadyGenerateAssets[$notGeneratedDesignItem['name']])) {
                    $assetEntityAddTempData['id'] = $alreadyGenerateAssets[$notGeneratedDesignItem['name']]['id'];
                    $assetEntityAddTempData['step_workflow_id'] = $alreadyGenerateAssets[$notGeneratedDesignItem['name']]['step_workflow_id'];
                    // 合并json字段数据 避免json字段被替换
                    $assetEntityAddTempData['json'] += $alreadyGenerateAssets[$notGeneratedDesignItem['name']]['json'];
                }
                $assetEntityAddData[] = $assetEntityAddTempData;

                // 资产可以关联资产
                if (in_array($notGeneratedDesignItem['category'], ['动作', '特效', '灯光'])) {
                    if (!empty($notGeneratedDesignItem['level_relate_asset'])) {
                        $relateAssetDict[$notGeneratedDesignItem['name']] = [];
                        foreach (explode(',', $notGeneratedDesignItem['level_relate_asset']) as $assetRelateAsset) {
                            $relateAssetDict[$notGeneratedDesignItem['name']][] = $assetRelateAsset;
                            $relateAssetNameList[$assetRelateAsset] = $assetRelateAsset;
                        }
                    }
                }
                // 资产可以关联关卡 特效
                if (in_array($notGeneratedDesignItem['category'], ['特效'])) {
                    if (!empty($notGeneratedDesignItem['relate_level'])) {
                        $relateLevelDict[$notGeneratedDesignItem['name']] = [];
                        foreach (explode(',', $notGeneratedDesignItem['relate_level']) as $assetRelateLevel) {
                            $relateLevelDict[$notGeneratedDesignItem['name']][] = $assetRelateLevel;
                            $relateLevelNameList[$assetRelateLevel] = $assetRelateLevel;
                        }
                    }
                }

            } else {
                if (!array_key_exists('level', $workflows)) {
                    throw_strack_exception("level step workflow does not exist.", ErrorCode::STEP_FLOW_DOES_NOT_EXIST);
                }

                $levelAttrData = [
                    'level_grade' => $notGeneratedDesignItem['grade'],
                    'level_style' => $notGeneratedDesignItem['style'],
                    'level_category' => $notGeneratedDesignItem['category'],
                    'level_design' => $notGeneratedDesignItem['id'],
                    'level_asset' => '', // 待资产添加后填充
                    'level_time_of_date' => $notGeneratedDesignItem['time_of_date'],
                    'level_weather' => $notGeneratedDesignItem['weather'],
                    'level_task' => $notGeneratedDesignItem['todo_task'],
                    "level_skip_remove_rigging_control" => $notGeneratedDesignItem['skip_remove_rigging_control'] ?? "yes",
                    'level_is_need_bind' => $notGeneratedDesignItem['is_need_bind'],
                    'level_special_effect_type' => $notGeneratedDesignItem['special_effect_type'],
                    'level_binding_attribute' => $notGeneratedDesignItem['binding_attribute'],
                    'level_solution_asset' => $notGeneratedDesignItem['solution_asset'],
                ];
                $levelAttrData = EntityService::getInstance()->mergeJsonFieldWithDefaultAttr($levelAttrData, $levelModuleDefaultAttr);

                $levelEntityAddTempData = [
                    'name' => $notGeneratedDesignItem['name'],
                    'code' => $notGeneratedDesignItem['code'],
                    'tenant_id' => $notGeneratedDesignItem['tenant_id'],
                    'project_id' => $notGeneratedDesignItem['project_id'],
                    'description' => $notGeneratedDesignItem['description'],
                    'module_id' => $levelModuleId,
                    'step_workflow_id' => $workflows['level']['id'],
                    'initial_episode_code' => $notGeneratedDesignItem['initial_episode_code'],
                    'showings_number' => $notGeneratedDesignItem['showings_number'],
                    'director_star' => $notGeneratedDesignItem['director_star'],
                    'key_shot' => $notGeneratedDesignItem['key_shot'],
                    'is_append' => $notGeneratedDesignItem['is_append'],
                    'json' => $levelAttrData,
                ];

                // 判断是否已经生成过
                if (isset($alreadyGenerateLevels[$notGeneratedDesignItem['name']])) {
                    $levelEntityAddTempData['id'] = $alreadyGenerateLevels[$notGeneratedDesignItem['name']]['id'];
                    $levelEntityAddTempData['step_workflow_id'] = $alreadyGenerateLevels[$notGeneratedDesignItem['name']]['step_workflow_id'];
                    // 合并json字段数据 避免json字段被替换
                    $levelEntityAddTempData['json'] += $alreadyGenerateLevels[$notGeneratedDesignItem['name']]['json'];
                }
                $levelEntityAddData[] = $levelEntityAddTempData;

                if (!empty($notGeneratedDesignItem['level_relate_asset'])) {
                    $relateAssetDict[$notGeneratedDesignItem['name']] = [];
                    foreach (explode(',', $notGeneratedDesignItem['level_relate_asset']) as $levelRelateAsset) {
                        $relateAssetDict[$notGeneratedDesignItem['name']][] = $levelRelateAsset;
                        $relateAssetNameList[$levelRelateAsset] = $levelRelateAsset;
                    }
                }
            }

            if (!empty($notGeneratedDesignItem['initial_episode_code']) && !in_array($notGeneratedDesignItem['initial_episode_code'], $episodeList)) {
                $episodeList[] = $notGeneratedDesignItem['initial_episode_code'];
            }
        }

        // 增量更新集数数据
        EntityService::getInstance()->appendGenerateEpisodes($projectId, $episodeList);

        $commonService = CommonService::instance('entity');
        $commonService->setCurrentModuleCode('design');
        // 开启事务生成资产和关卡
        $entityModel->startTrans(); //强制开启事务
        try {
            $updateEntityData = [];
            // 批量新增资产
            foreach ($assetEntityAddData as &$assetEntity) {
                //新增资产
                if (isset($assetEntity['id']) && $assetEntity['id'] > 0) {
                    $updateEntityData[] = $assetEntity;
                    // 存在就更新
                    $res['asset_updated'][] = $entityModel->modifyItem($assetEntity);
                    $assetPublished[] = $assetEntity['id'];
                } else {
                    $res['asset_added'][] = $addFlag = $entityModel->addItem($assetEntity);
                    if ($addFlag === false && $entityModel->getErrorCode() !== \think\exception\ErrorCode::DATA_ALREADY_EXISTS) {
                        throw new LogicException('add error ' . $entityModel->getError(), ErrorCode::ENTITY_ADD_FAILURE);
                    }
                    if ($addFlag !== false) {
                        $assetPublished[] = $addFlag['id'];
                    }
                    $assetEntity['id'] = $addFlag['id'];
                }
                $relateAssetNameList[$assetEntity['name']] = $assetEntity['name'];
            }

            $assetDict = [];
            if (!empty($relateAssetNameList)) {
                $assetData = $entityModel
                    ->field("id,name")
                    ->where([
                        'project_id' => $projectId,
                        'module_id' => $assetModuleId,
                        'tenant_id' => $tenantId,
                        'name' => ['IN', implode(',', $relateAssetNameList)],
                    ])->select();
                $assetDict = array_column($assetData, 'id', 'name');
            }

            // 填充资产数据
            foreach ($levelEntityAddData as &$levelItem) {
                // 关卡关联的资产
                $relateAssetIds = [];
                if (!empty($relateAssetDict[$levelItem['name']])) {
                    foreach ($relateAssetDict[$levelItem['name']] as $assetName) {
                        if (array_key_exists($assetName, $assetDict)) {
                            $relateAssetIds[] = $assetDict[$assetName];
                        }
                    }
                }

                $levelItem['json']['level_asset'] = join(',', $relateAssetIds);
                // 新增关卡
                if (isset($levelItem['id']) && $levelItem['id'] > 0) {
                    $updateEntityData[] = $levelItem;
                    // 存在就更新
                    $res['level_updated'][] = $entityModel->modifyItem($levelItem);
                    $levelPublished[] = $levelItem['id'];
                } else {
                    $res['level_added'][] = $addFlag = $entityModel->addItem($levelItem);
                    if ($addFlag === false && $entityModel->getErrorCode() !== \think\exception\ErrorCode::DATA_ALREADY_EXISTS) {
                        throw new LogicException('add error ' . $entityModel->getError(), ErrorCode::ENTITY_ADD_FAILURE);
                    }
                    if ($addFlag !== false) {
                        $levelPublished[] = $addFlag['id'];
                    }
                }
            }
            // 查询关联的关卡信息
            $levelDict = [];
            if (!empty($relateLevelNameList)) {
                $levelData = $entityModel
                    ->field("id,name")
                    ->where([
                        'project_id' => $projectId,
                        'module_id' => $levelModuleId,
                        'tenant_id' => $tenantId,
                        'name' => ['IN', implode(',', $relateLevelNameList)],
                    ])->select();
                $levelDict = array_column($levelData, 'id', 'name');
            }
            // 填充资产的关联字段情况
            // todo 改成异步处理 生成任务对关联信息不会有特殊要求
            $this->reFillAssetRelationData($assetEntityAddData, $relateAssetDict, $assetDict, $relateLevelDict, $levelDict);

            $taskGenerateService = TaskGenerateService::getInstance();
            // 生成任务
            if (count($assetPublished) > 0) {
                $assetPublishedIdStr = implode(',', $assetPublished);
                $res['asset_task_published'] = $taskGenerateService->doGenerateEntityStepCategoryTask($projectId, 'asset', $assetPublishedIdStr, $tenantId, $entityStatus, $taskDefaultStatus, $stepCategoryTaskDefaultValue);
            }
            if (count($levelPublished) > 0) {
                $levelPublishedIdStr = implode(',', $levelPublished);
                $res['level_task_published'] = $taskGenerateService->doGenerateEntityStepCategoryTask($projectId, 'level', $levelPublishedIdStr, $tenantId, $entityStatus, $taskDefaultStatus, $stepCategoryTaskDefaultValue);
            }
            // 更新概念的状态为已生成
            foreach ($notGeneratedDesignData as $notGeneratedDesignItem) {
                $entityUpdateData = [
                    'id' => $notGeneratedDesignItem['id'],
                    'is_generated_follow' => 'yes',
                ];
                if (!empty($designStatus) && !in_array($notGeneratedDesignItem['status'], $publishedStatusIdList)) {
                    $entityUpdateData['design_status'] = $designStatus['id'];
                }
                $commonService->update($entityUpdateData, 'design', false);

            }
            // 同步更新任务
            if (!empty($updateEntityData)) {
                // 更新工序流id
                TaskService::getInstance()->entityPublishedUpdateTaskStepWorkflowId($updateEntityData);
            }
            $entityModel->commit();
        } catch (Throwable $e) {
            $entityModel->rollback();
            throw $e;
        }

        try {
            foreach ($assetPublished as $assetId) {
                Client::send('design_publish_check', build_queue_data_with_xu([
                    'id' => $assetId,
                    'module_code' => 'asset',
                ]), 30);
                Client::send('task_relation_update_by_entity_publish', build_queue_data_with_xu(['entity_id' => $assetId]));
            }

            foreach ($levelPublished as $levelId) {
                Client::send('design_publish_check', build_queue_data_with_xu([
                    'id' => $levelId,
                    'module_code' => 'level',
                ]), 30);
                Client::send('task_relation_update_by_entity_publish', build_queue_data_with_xu(['entity_id' => $levelId]));
            }
        } catch (Throwable $e) {
            // 发布检查发生异常
            trace(format_exception_info($e), 'ERR');
        }
        return $res;

    }


    /**
     * 生成实体任务
     * @param int $projectId
     * @param int $tenantId
     * @param array $filter
     * @param string $entityStatus
     * @param array $stepCategoryTaskDefaultValue
     * @return array
     * @throws Exception
     * @throws Throwable
     */
    public function generateEntityTask(int $projectId, int $tenantId, array $filter = [], string $entityStatus = 'task_generated', array $stepCategoryTaskDefaultValue = []): array
    {
        $entityFilter = [
            'project_id' => $projectId,
            'tenant_id' => $tenantId,
        ];
        if (!empty($filter)) {
            $entityFilter['_complex'] = $filter;
        }

        $publishedStatusList = StatusService::getInstance()->getEntityPublishedStatusList('design');
        $publishedStatusIdList = array_column($publishedStatusList, 'id');
        $entityStatus = StatusService::getInstance()->getOne(['code' => $entityStatus], '*');

        $entityFields = [
            "entity.id",
            "entity.code",
            "entity.initial_episode_code",
            "module.code as module_code",
            "JSON_UNQUOTE(JSON_EXTRACT(entity.json, CONCAT('$.', module.code, '_category'))) AS category",
            "JSON_UNQUOTE(JSON_EXTRACT(entity.json, CONCAT('$.', module.code, '_status'))) AS status"
        ];
        $notGeneratedTaskData = model(EntityModel::class)
            ->join('module on module.id = entity.module_id')
            ->field($entityFields)
            ->where($entityFilter)->select();

        $res = [];
        if (empty($notGeneratedTaskData)) {
            return $res;
        }

        $episodeList = []; // 所属集列表
        $published = [];

        // 添加待生成任务的数据
        foreach ($notGeneratedTaskData as $notGeneratedTaskItem) {
            $published[] = $notGeneratedTaskItem['id'];
            if (!empty($notGeneratedTaskItem['initial_episode_code']) && !in_array($notGeneratedTaskItem['initial_episode_code'], $episodeList)) {
                $episodeList[] = $notGeneratedTaskItem['initial_episode_code'];
            }
        }

        if (empty($published)) {
            return $res;
        }

        // 增量更新集数数据
        EntityService::getInstance()->appendGenerateEpisodes($projectId, $episodeList);

        $commonService = CommonService::instance('entity');
        $commonService->setCurrentModuleCode('design');
        // 开启事务生成资产和关卡
        $entityModel = model(EntityModel::class);
        $entityModel->startTrans(); // 强制开启事务
        try {
            $taskGenerateService = TaskGenerateService::getInstance();
            // 生成任务
            $publishedIdStr = implode(',', $published);
            $res['task_published'] = $taskGenerateService->doGenerateEntityStepCategoryTaskV3($projectId, $publishedIdStr, $tenantId, $entityStatus['code'], $stepCategoryTaskDefaultValue);
            // 更新概念的状态为已生成
            foreach ($notGeneratedTaskData as $notGeneratedTaskItem) {
                $entityUpdateData = [
                    'id' => $notGeneratedTaskItem['id'],
                    'is_generated_follow' => 'yes',
                ];
                if (!empty($entityStatus) && !in_array($notGeneratedTaskItem['status'], $publishedStatusIdList)) {
                    $entityUpdateData[$notGeneratedTaskItem['module_code'] . '_status'] = $entityStatus['id'];
                }
                $commonService->update($entityUpdateData, $notGeneratedTaskItem['module_code'], false);
            }
            $entityModel->commit();
        } catch (Throwable $e) {
            $entityModel->rollback();
            throw $e;
        }
        return $res;
    }

    /**
     * 通过分镜数据生成镜头
     * @param int $projectId
     * @param int $tenantId
     * @param array $filter
     * @param string $entityStatus
     * @param string $taskDefaultStatus
     * @return array
     * @throws Exception
     * @throws Throwable
     */
    public function generateShotByStoryboard(int $projectId, int $tenantId, $filter = [], string $entityStatus = 'task_generated', string $taskDefaultStatus = '', $stepCategoryTaskDefaultValue = [])
    {
        /**
         * 序列工序流
         * 1、手K动画-布料解算序列：storyboard_production_method=手K;storyboard_cloth_solution=高质量解算
         * 2、手K动画序列：storyboard_production_method=手K;storyboard_cloth_solution=引擎解算
         * 3、动捕动画-布料解算序列：storyboard_production_method=动捕;storyboard_cloth_solution=高质量解算
         * 4、动捕动画序列：storyboard_production_method=动捕;storyboard_cloth_solution=引擎解算
         *
         * 序列跟分镜对应字段
         * 1、序列名称 name：分镜自定义字段 storyboard_sequence
         * 2、序列所属集 entity_id：通过分镜初始集字段查找取得 initial_episode_code，不存在创建
         * 3、预估工时 estimate_hours ：根据工序流计算得到
         * 3、序列类型 sequence_type ： 分镜自定义字段 storyboard_type
         * 4、表演形式 sequence_perform_form ： 分镜自定义字段 storyboard_perform_form
         * 5、表演人数 storyboard_perform_numbers ： 分镜自定义字段 sequence_perform_numbers
         * 6、表演种族 sequence_perform_species ： 分镜自定义字段 storyboard_perform_species
         * 7、制作方式 sequence_production_method ： 分镜自定义字段 storyboard_production_method
         * 8、序列时长 sequence_duration ： 当前序列对应多个分镜 duration 累加匹配的属性范围
         * 9、风格 sequence_style ： 分镜自定义字段 storyboard_style
         * 10、绑定关卡 sequence_level : 分镜自定义字段 storyboard_level
         */

        /**
         * 镜头工序流
         * 1、无特效镜头：storyboard_fx=none
         * 2、其他特效镜头：storyboard_fx=Houdini
         * 3、引擎特效镜头：storyboard_fx=Niagara
         * 4、混合特效镜头：storyboard_fx=Houdini+Niagara
         *
         * 镜头跟分镜对应字段
         * 1、镜头名称 name：分镜名称 name
         * 2、所属序列 entity_id：序列创建完毕后得到 id
         * 3、预估工时 estimate_hours ：根据工序流计算得到
         * 3、序列类型 shot_type ： 分镜自定义字段 storyboard_type
         * 4、表演形式 shot_perform_form ： 分镜自定义字段 storyboard_perform_form
         * 5、表演人数 shot_perform_numbers ： 分镜自定义字段 sequence_perform_numbers
         * 6、表演种族 shot_perform_species ： 分镜自定义字段 storyboard_perform_species
         * 7、制作方式 shot_production_method ： 分镜自定义字段 storyboard_production_method
         * 8、序列时长 shot_duration ： 当前对应分镜 duration 匹配的属性范围
         * 9、风格 shot_style ： 分镜自定义字段 storyboard_style
         */

        /**
         * 镜头时长
         * 1、~3：0-3秒
         * 2、~6：3-6秒
         * 3、~9: 6-9秒
         * 4、~12: 9-12秒
         * 5、~15: 12-15秒
         * 6、15~：>15秒
         */

        $storyboardModuleId = Module::$moduleDictData['module_index_by_code']['storyboard']['id'];
        $episodeModuleId = Module::$moduleDictData['module_index_by_code']['episode']['id'];
        $sequenceModuleId = Module::$moduleDictData['module_index_by_code']['sequence']['id'];
        $shotModuleId = Module::$moduleDictData['module_index_by_code']['shot']['id'];

        // 1. 取出所有未生成后续任务分镜 is_generated_follow = no
        $entityModel = model(EntityModel::class);

        $entityFilter = [
            'project_id' => $projectId,
            'module_id' => $storyboardModuleId,
            'tenant_id' => $tenantId,
//            'is_generated_follow' => 'no',
        ];

        if (!empty($filter)) {
            $entityFilter['_complex'] = $filter;
        }

        $storyboardStatus = model(StatusModel::class)->where(['code' => $entityStatus])->find();
        $projectTemplateId = model(ProjectModel::class)->where(['id' => $projectId])->value('project_template_id');

        $fields = "
            id,
            name,
            code,
            tenant_id,
            project_id,
            duration,
            description,
            initial_episode_code,
            showings_number,
            is_confirm,
            is_append,
            director_star,
            key_shot,
            json
            ";
        $notGeneratedStoryboardData = $entityModel
            ->field($fields)
            ->where($entityFilter)
            ->select();
        foreach ($notGeneratedStoryboardData as &$notGeneratedStoryboardDataItem) {
            CommonService::getFormatAttrData(model(EntityModel::class), $notGeneratedStoryboardDataItem);
            $entityJson = [];
            foreach ($notGeneratedStoryboardDataItem['json'] as $fieldKey => $configArrayItem) {
                $entityJson[str_replace_once('storyboard' . '_', '', $fieldKey)] = $configArrayItem;
            }
            $notGeneratedStoryboardDataItem['json'] = $entityJson;
            $notGeneratedStoryboardDataItem = array_merge($notGeneratedStoryboardDataItem['json'], $notGeneratedStoryboardDataItem);
        }

        $storyboardSequenceIdList = array_column($notGeneratedStoryboardData, 'motion_sequence', 'motion_sequence');
        $storyboardSequenceIdList = array_filter($storyboardSequenceIdList, function ($itemId) {
            return intval($itemId) > 0;
        });
        $storyboardSequenceList = [];
        if (!empty($storyboardSequenceIdList)) {
            $storyboardSequenceList = $this->entityModel->where(['id' => ['IN', implode(',', $storyboardSequenceIdList)]])->field("id,name,code,json->>'$.sequence_status' as sequence_status")->select();
            $storyboardSequenceList = array_column($storyboardSequenceList, null, 'id');
        }

        $episodeList = []; // 所属集列表
        $sequenceEntityAddData = []; // 待添加序列数据
        $sequenceEntitySortData = [];
        $sequenceEntityWorkflowParam = [];
        $motionVersionShotStepWorkflowCodeConfig = OptionsService::getInstance()->getOptionsConfigItemData('task_config', 'motion_shot_step_workflow_code', 'motion_shot');
        $motionVersionSequenceStepWorkflowCodeConfig = OptionsService::getInstance()->getOptionsConfigItemData('task_config', 'motion_sequence_step_workflow_code', 'motion_sequence');
        $workflowCodes = ['shot', 'sequence', $motionVersionShotStepWorkflowCodeConfig, $motionVersionSequenceStepWorkflowCodeConfig];

        $publishedStatusList = StatusService::getInstance()->getEntityPublishedStatusList('storyboard');
        $publishedStatusIdList = array_column($publishedStatusList, 'id');
        $workflowList = model(StepWorkflowModel::class)
            ->where([
                'code' => ['IN', implode(',', $workflowCodes)],
                'project_id' => ['IN', [0, $projectId]],
                'project_template_id' => 0,
                'is_enable' => 'yes'
            ])
            ->order('project_id asc')
            ->select();
        $workflows = [];
        foreach ($workflowList as $workflowItem) {
            // 默认取项目id为0的工序流配置,如果有配置项目,就取项目的工序流配置
            if (empty($workflows[$workflowItem['code']] ?? []) || $workflows[$workflowItem['code']]['project_id'] == 0) {
                $workflows[$workflowItem['code']] = $workflowItem;
            }
        }
        $res = [];
        if (empty($notGeneratedStoryboardData)) {
            return $res;
        }

//        $doneStatus = StatusService::getInstance()->getTaskDoneStatus();
        $sequencePublished = [];
        $shotPublished = [];
        $shotModuleDefaultAttr = ModuleService::getInstance()->getModuleDefaultAttr('shot');
        $sequenceModuleDefaultAttr = ModuleService::getInstance()->getModuleDefaultAttr('sequence');
        foreach ($notGeneratedStoryboardData as $notGeneratedStoryboardItem) {
            if (empty($notGeneratedStoryboardItem['initial_episode_code'])) {
                throw new LogicException('storyboard initial_episode_code cant be empty', ErrorCode::MISSING_INITIAL_EPISODE_CODE);
            }
            $notGeneratedStoryboardItem['is_new_shot_step_workflow'] = false;
            // 判断是动捕还是手k
            if ($notGeneratedStoryboardItem['production_method'] === '捕捉') {
                // 捕捉镜头发布的时候 必须要有序列
//                if ((empty($notGeneratedStoryboardItem['motion_sequence']) || empty($storyboardSequenceList[$notGeneratedStoryboardItem['motion_sequence']])) && empty($notGeneratedStoryboardItem['sequence'])) {
//                    throw new LogicException("motion capture shot must have motion sequence", ErrorCode::MOTION_CAPTURE_SHOT_MUST_HAVE_MOTION_SEQUENCE);
//                }
                if (!empty($notGeneratedStoryboardItem['motion_sequence'])) {
                    $notGeneratedStoryboardItem['sequence'] = $storyboardSequenceList[$notGeneratedStoryboardItem['motion_sequence']]['name'] ?? '';
                    // @update 2023年7月22日 移除对动捕任务完成的判断
//                    if ($storyboardSequenceList[$notGeneratedStoryboardItem['motion_sequence']]['sequence_status'] != $doneStatus['id']) {
//                        throw new LogicException('motion sequence task must be completed', ErrorCode::MOTION_SEQUENCE_TASK_MUST_BE_COMPLETED);
//                    }
                    $notGeneratedStoryboardItem['is_new_shot_step_workflow'] = true;
                }
                // 捕捉任务走 motion_shot 工序流
                $notGeneratedStoryboardItem['is_new_shot_step_workflow'] = true;
            } elseif (strcasecmp($notGeneratedStoryboardItem['production_method'], '手K') === 0) {
                // 手k没有序列 走新的工序流
                if (empty($notGeneratedStoryboardItem['sequence'])) {
                    $notGeneratedStoryboardItem['is_new_shot_step_workflow'] = true;
                }
            }

            if (empty($notGeneratedStoryboardItem['sequence'])) {
                $episodeSequenceKey = 'null_sequence';
            } else {
                $episodeSequenceKey = "{$notGeneratedStoryboardItem['initial_episode_code']}_{$notGeneratedStoryboardItem['sequence']}";
            }

            $sequenceEntitySortData[$episodeSequenceKey][] = $notGeneratedStoryboardItem;

            if ($episodeSequenceKey != 'null_sequence') {
                // 序列关联关卡
                $storyboardLevel = [];
                if (!empty($notGeneratedStoryboardItem['level'])) {
                    $storyboardLevel = explode(',', $notGeneratedStoryboardItem['level']);
                }
                // 序列关联资产
                $storyboardAsset = [];
                if (!empty($notGeneratedStoryboardItem['asset'])) {
                    $storyboardAsset = explode(',', $notGeneratedStoryboardItem['asset']);
                }
                EntityService::getInstance()->checkSequenceShotAttr($episodeSequenceKey, $notGeneratedStoryboardItem, $storyboardAsset, $storyboardLevel, $sequenceEntityWorkflowParam);
            }

            if (!in_array($notGeneratedStoryboardItem['initial_episode_code'], $episodeList)) {
                $episodeList[] = $notGeneratedStoryboardItem['initial_episode_code'];
            }
        }

        // 增量更新集数数据
        EntityService::getInstance()->appendGenerateEpisodes($projectId, $episodeList);

        // 获取集数映射字典
        $episodeData = $entityModel->field('id,name,code')->where([
            'project_id' => $projectId,
            'module_id' => $episodeModuleId,
        ])->select();

        $episodeDict = array_column($episodeData, null, 'code');
        foreach ($sequenceEntitySortData as $episodeSequenceKey => $shotList) {

            // 处理镜头数据除了关联序列
            $shotAddData = [];
            $showingNumber = '';
            if (empty($workflows['shot'])) {
                throw_strack_exception('missing shot workflow', ErrorCode::STEP_WORKFLOW_CONFIG_NOT_FOUND);
            }
            $sequenceClothSolution = '';
            $currentInitialEpisodeCode = '';
            $style = '';
            $sequenceName = '';
            $description = '';
            $motionSequenceId = 0;
            foreach ($shotList as $shotItem) {
                if (empty($showingNumber)) {
                    $showingNumber = $shotItem['showings_number'];
                }
                if (empty($motionSequenceId) && !empty($shotItem['motion_sequence'])) {
                    $motionSequenceId = $shotItem['motion_sequence'];
                }
                if (empty($sequenceClothSolution)) {
                    $sequenceClothSolution = $shotItem['cloth_solution'];
                }
                if (empty($currentInitialEpisodeCode)) {
                    $currentInitialEpisodeCode = $shotItem['initial_episode_code'];
                }
                if (empty($style)) {
                    $style = $shotItem['style'];
                }
                if (empty($sequenceName)) {
                    $sequenceName = $shotItem['sequence'];
                }
                if (empty($description)) {
                    $description = $shotItem['description'];
                }
                $shotAttrData = [];
                foreach ($shotItem['json'] as $jsonKey => $jsonValue) {
                    if ($jsonKey === 'duration') {
                        $jsonValue = AttributeValueService::getInstance()->getDurationAttribute($jsonValue);
                    }
                    $shotAttrData['shot_' . $jsonKey] = $jsonValue;
                }

                $shotAttrData['shot_storyboard'] = $shotItem['id'];

                $shotAttrData = EntityService::getInstance()->mergeJsonFieldWithDefaultAttr($shotAttrData, $shotModuleDefaultAttr);

                $shotWorkflowId = $workflows['shot']['id'];
                if ($shotItem['is_new_shot_step_workflow'] && !empty($workflows[$motionVersionShotStepWorkflowCodeConfig])) {
                    $shotWorkflowId = $workflows[$motionVersionShotStepWorkflowCodeConfig]['id'];
                }
                $tmpShot = [
                    'name' => $shotItem['name'],
                    'code' => generate_code($shotItem['name'], ""),
                    'description' => $description,
                    'tenant_id' => $tenantId,
                    'project_id' => $projectId,
                    'module_id' => $shotModuleId,
                    'step_workflow_id' => $shotWorkflowId,
                    'initial_episode_code' => $shotItem['initial_episode_code'],
                    'entity_id' => 0, // 待序列添加后写入
                    'entity_module_id' => $sequenceModuleId,
                    'duration' => $shotItem['duration'],
                    'created_by' => fill_created_by(),
                    'created' => time(),
                    'uuid' => create_uuid(),
                    'showings_number' => $shotItem['showings_number'],
                    'is_confirm' => $shotItem['is_confirm'],
                    'director_star' => $shotItem['director_star'],
                    'key_shot' => $shotItem['key_shot'],
                    'is_append' => $shotItem['is_append'],
                    'json' => $shotAttrData,
                ];

                // 判断镜头是否已经生成
                $existShot = $this->entityModel
                    ->where([
                        'tenant_id' => $tenantId, 'project_id' => $projectId, 'module_id' => $shotModuleId,
                    ])
                    ->where("JSON_UNQUOTE(JSON_EXTRACT(json,'$.shot_storyboard')) = {$shotItem['id']}")
                    ->field('id,json')
                    ->find();
                if (!empty($existShot)) {
                    // 已经存在 就合并json数据
                    $tmpShot['id'] = $existShot['id'];
                    $existShot['json'] = json_decode($existShot['json'] ?? '{}', true);
                    if (!empty($existShot['json'])) {
                        $tmpShot['json'] += $existShot['json'];
                    }
                }
                $tmpShot['json'] = json_encode($tmpShot['json']);
                $shotAddData[] = $tmpShot;
            }
            $sequenceData = null;
            if ($episodeSequenceKey !== 'null_sequence') {
                if (empty($workflows['sequence'])) {
                    throw_strack_exception('missing sequence workflow', ErrorCode::STEP_WORKFLOW_CONFIG_NOT_FOUND);
                }
                $sequenceWhere = [
                    'tenant_id' => $tenantId,
                    'module_id' => $storyboardModuleId,
                    'project_id' => $projectId,
                    'initial_episode_code' => $currentInitialEpisodeCode,
                ];
                if ($motionSequenceId > 0) {
                    // 新动捕流程
                    $sequenceWhere['_string'] = "JSON_UNQUOTE(JSON_EXTRACT(json,'$.storyboard_motion_sequence')) = {$motionSequenceId}";
                } else {
                    // 老的组装序列流程
                    $sequenceWhere['_string'] = "JSON_UNQUOTE(JSON_EXTRACT(json,'$.storyboard_sequence')) = '{$sequenceName}'";
                }
                $currentSequenceShotList = $this->entityModel
                    ->where($sequenceWhere)
                    ->field($fields)
                    ->select();

                $sequenceAttr = SequenceService::getInstance()->mergeSequenceAttrByStoryboards($currentSequenceShotList);

                // 序列数据
                $sequenceData = [
                    'name' => $sequenceName,
                    'code' => generate_code($sequenceName, ""),
                    'description' => $description,
                    'tenant_id' => $tenantId,
                    'project_id' => $projectId,
                    'module_id' => $sequenceModuleId,
                    'step_workflow_id' => $workflows['sequence']['id'],
                    'initial_episode_code' => $currentInitialEpisodeCode,
                    'entity_id' => $episodeDict[$currentInitialEpisodeCode]['id'],
                    'entity_module_id' => $episodeModuleId,
                    'showings_number' => $showingNumber,
                    'uuid' => create_uuid(),
                    'json' => [
                        'sequence_style' => $style,
                        'sequence_perform_species' => $sequenceAttr['sequence_perform_species'],
                        'sequence_perform_numbers' => $sequenceAttr['sequence_perform_numbers'],
                        'sequence_production_method' => $sequenceAttr['sequence_production_method'],
                        'sequence_perform_form' => $sequenceAttr['sequence_perform_form'],
                        'sequence_duration' => $sequenceAttr['sequence_duration'],
                        'sequence_level' => $sequenceAttr['sequence_level'],
                        'sequence_asset' => $sequenceAttr['sequence_asset'],
//                        'sequence_showings_number' => $showingNumber,
                        'sequence_cloth_solution' => $sequenceClothSolution, // 布料
                        'sequence_is_cluster' => $sequenceAttr['sequence_is_cluster'],
                    ],
                ];
                $sequenceData['json'] = EntityService::getInstance()->mergeJsonFieldWithDefaultAttr($sequenceData['json'], $sequenceModuleDefaultAttr);
                // 判断sequence存不存在
                $existSequence = $this->entityModel
                    ->where([
                        'tenant_id' => $tenantId, 'project_id' => $projectId, 'module_id' => $sequenceModuleId, 'initial_episode_code' => $currentInitialEpisodeCode,
                        'name' => $sequenceName,
                    ])
                    ->field('id,json')
                    ->find();
                if (!empty($existSequence)) {
                    $sequenceData['id'] = $existSequence['id'];
                    unset($sequenceData['step_workflow_id']); // 避免替换序列的工序流id
                    $existSequence['json'] = json_decode($existSequence['json'] ?? '{}', true);
                    if (!empty($existSequence['json'])) {
                        $sequenceData['json'] += $existSequence['json'];
                    }
                }
            }

            $sequenceEntityAddData[] = [
                'sequence' => $sequenceData,
                'shots' => $shotAddData,
            ];
        }

        $commonService = CommonService::instance('entity');
        $commonService->setCurrentModuleCode('storyboard');
        // 开启事务生成序列和镜头
        $entityModel->startTrans(); //强制开启事务
        try {
            $updateEntityToTaskData = [];
            foreach ($sequenceEntityAddData as $addSequenceData) {
                $insertSequenceId = 0;
                if (isset($addSequenceData['sequence']['id']) && $addSequenceData['sequence']['id'] > 0) {
                    $insertSequenceId = $addSequenceData['sequence']['id'];
                    // 更新属性
                    $res['sequence_updated'][] = $updateResult = $this->entityModel->modifyItem(['id' => $insertSequenceId, 'json' => $addSequenceData['sequence']['json']]);
                    if ($updateResult === false && $this->entityModel->getErrorCode() !== \think\exception\ErrorCode::NO_DATA_HAS_BEEN_CHANGED) {
                        throw new LogicException('update error ' . $entityModel->getError(), ErrorCode::COMMON_UPDATE_FAILURE);
                    }
                } else if (!empty($addSequenceData['sequence'])) {
                    $res['sequence_added'][] = $addResult = $entityModel->addItem($addSequenceData['sequence']);
                    if (!$addResult) {
                        // 添加失败错误码 001
                        $errorCode = !empty($entityModel->getErrorCode()) ? $entityModel->getErrorCode() : -202012;
                        if ($errorCode === \think\exception\ErrorCode::DATA_ALREADY_EXISTS) {
                            // 已经存在数据
                            $insertSequenceId = $entityModel->getCheckUniqueExitData()['id'];
                        } else {
                            throw new LogicException($entityModel->getError(), ErrorCode::ENTITY_ADD_FAILURE);
                        }
                    } else {
                        // 返回成功数据
                        $insertSequenceId = $addResult['id'];
                    }
                }

                // 动捕序列不需要进行发布任务操作
                if (!empty($insertSequenceId) &&
                    (empty($addSequenceData['sequence']['json']['sequence_type']) || $addSequenceData['sequence']['json']['sequence_type'] !== 'motion_capture')
                ) {
                    $sequencePublished[] = $insertSequenceId;
                }

                // 追加序列id
                foreach ($addSequenceData['shots'] as &$shotItem) {
                    $shotItem['entity_id'] = $insertSequenceId;

                    if (isset($shotItem['id']) && $shotItem['id'] > 0) {
                        // 镜头已经存在 就更新属性,时长,关联信息
                        $updateData = [
                            'id' => $shotItem['id'],
                            'json' => $shotItem['json'],
                            'duration' => $shotItem['duration'],
                            'initial_episode_code' => $shotItem['initial_episode_code'],
                            'entity_id' => $shotItem['entity_id'],
                            'entity_module_id' => $shotItem['entity_module_id'],
                            'step_workflow_id' => $shotItem['step_workflow_id'],
                        ];
                        $res['shot_updated'][] = $updateResult = $this->entityModel->save($updateData);
                        if ($updateResult === false && $this->entityModel->getErrorCode() !== \think\exception\ErrorCode::NO_DATA_HAS_BEEN_CHANGED) {
                            throw new LogicException('update error ' . $entityModel->getError(), ErrorCode::COMMON_UPDATE_FAILURE);
                        }
                        $shotPublished[] = $shotItem['id'];
                        $updateEntityToTaskData[] = $shotItem;
                    } else {
                        $res['shot_added'][] = $shotId = $entityModel->add($shotItem);
                        $shotPublished[] = $shotId;
                    }
                }

                // 批量新增镜头
                //                    $entityModel->addAll($addSequenceData['shots']);
            }

            $taskGenerateService = TaskGenerateService::getInstance();
            // 生成任务
            if (count($sequencePublished) > 0) {
                $sequencePublishedIdStr = implode(',', $sequencePublished);
                $res['sequence_task_published'] = $taskGenerateService->doGenerateEntityStepCategoryTask($projectId, 'sequence', $sequencePublishedIdStr, $tenantId, $entityStatus, $taskDefaultStatus, $stepCategoryTaskDefaultValue);
            }
            if (count($shotPublished) > 0) {
                $shotPublishedIdStr = implode(',', $shotPublished);
                $res['shot_task_published'] = $taskGenerateService->doGenerateEntityStepCategoryTask($projectId, 'shot', $shotPublishedIdStr, $tenantId, $entityStatus, $taskDefaultStatus, $stepCategoryTaskDefaultValue);
            }

            // 更新分镜的状态为已生成
            foreach ($notGeneratedStoryboardData as $notGeneratedStoryboardItem) {
                $entityUpdateData = [
                    'id' => $notGeneratedStoryboardItem['id'],
                    'is_generated_follow' => 'yes',
                ];
                if (!empty($storyboardStatus) && !in_array($notGeneratedStoryboardItem['status'], $publishedStatusIdList)) {
                    $entityUpdateData['storyboard_status'] = $storyboardStatus['id'];
                }
                $commonService->update($entityUpdateData, 'storyboard', false);
            }
            if (!empty($updateEntityToTaskData)) {
                TaskService::getInstance()->entityPublishedUpdateTaskStepWorkflowId($updateEntityToTaskData);
            }

            $entityModel->commit();
        } catch (Throwable $e) {
            $entityModel->rollback();
            throw $e;
        }

        try {
            foreach ($sequencePublished as $sequenceId) {
                Client::send('task_relation_update_by_entity_publish', build_queue_data_with_xu(['entity_id' => $sequenceId]));
            }
            foreach ($shotPublished as $shotId) {
                Client::send('task_relation_update_by_entity_publish', build_queue_data_with_xu(['entity_id' => $shotId]));
            }
        } catch (Throwable $e) {
            // 发布检查发生异常
            trace(format_exception_info($e), 'ERR');
        }

        return $res;

    }

    /**
     * 处理资产的关联字段
     *
     * @param array $assetEntityAddData 资产列表
     * @param array $relateAssetDict 资产关联资产分组map
     * @param array $assetDict 相关联的所有的资产id name map
     * @param array $relateLevelDict 资产关联关卡分组map
     * @param array $levelDict 相关联的所有的关卡id name map
     * @return void
     */
    public function reFillAssetRelationData($assetEntityAddData, $relateAssetDict, $assetDict, $relateLevelDict, $levelDict)
    {
        // 再次处理关联
        foreach ($assetEntityAddData as &$assetEntity) {
            $relateAssetIds = [];
            if (!empty($relateAssetDict[$assetEntity['name']])) {
                foreach ($relateAssetDict[$assetEntity['name']] as $assetName) {
                    if (array_key_exists($assetName, $assetDict)) {
                        $relateAssetIds[] = $assetDict[$assetName];
                    }
                }
            }
            $relateAssetIds = implode(',', $relateAssetIds);
            $relateLevelIds = [];
            if (!empty($relateLevelDict[$assetEntity['name']])) {
                foreach ($relateLevelDict[$assetEntity['name']] as $levelName) {
                    if (array_key_exists($levelName, $levelDict)) {
                        $relateLevelIds[] = $levelDict[$levelName];
                    }
                }
            }
            $relateLevelIds = implode(',', $relateLevelIds);
            $this->entityModel->execute("update entity set json = json_set(json, '$.asset_asset','{$relateAssetIds}', '$.asset_level', '{$relateLevelIds}') where id = {$assetEntity['id']}");
        }
    }

    /**
     * 概念发布 检查关联
     * @param $entityId
     * @return bool|int
     * @throws Exception
     */
    public function designPublishCheckRelated($entityId)
    {
        $entity = model(EntityModel::class)->find($entityId);
        if (empty($entity)) {
            throw new LogicException('entity not found', ErrorCode::ENTITY_NOT_FOUND);
        }
        $relatedField = '';
        $publishedModule = Module::$moduleDictData['module_index_by_id'][$entity['module_id']];

        switch ($publishedModule['code']) {
            case 'asset':
                $relatedField = 'level_relate_asset';

                break;
            case 'level':
                $relatedField = 'design_relate_level';
                break;
            default:
        }
        if (empty($relatedField)) {
            return false;
        }

        // 查找出关联了该实体的概念
        $relatedDesignList = $this->entityModel
            ->where([
                'tenant_id' => $entity['tenant_id'],
                'project_id' => $entity['project_id'],
            ])
            ->where("FIND_IN_SET('{$entity['name']}',JSON_UNQUOTE(JSON_EXTRACT(json,'$.{$relatedField}'))) ")
            ->field('entity.*,' . "JSON_UNQUOTE(JSON_EXTRACT(json,'$.{$relatedField}')) as related_str")
            ->select();

        if (empty($relatedDesignList)) {
            return true;
        }

        $relatedDesignMap = array_column($relatedDesignList, null, 'id');

        $relatedDesignIdList = array_column($relatedDesignList, 'id');

        // 概念对应的资产
        $relatedEntityList = $this->entityModel
            ->where([
                'tenant_id' => $entity['tenant_id'],
                'project_id' => $entity['project_id'],
            ])
            ->where("JSON_UNQUOTE(JSON_EXTRACT(json,'$.asset_design')) IN (" . implode(',', $relatedDesignIdList) . ")")
            ->field('entity.*,' . "json_unquote(json_extract(json,'$.asset_design')) as design_id")
            ->select();

        // 概念对应的关卡
        $relatedLevelEntityList = $this->entityModel
            ->where([
                'tenant_id' => $entity['tenant_id'],
                'project_id' => $entity['project_id'],
            ])
            ->where("JSON_UNQUOTE(JSON_EXTRACT(json,'$.level_design')) IN (" . implode(',', $relatedDesignIdList) . ")")
            ->field('entity.*,' . "json_unquote(json_extract(json,'$.level_design')) as design_id")
            ->select();

        $relatedEntityList = array_merge($relatedEntityList, $relatedLevelEntityList);
        $relatedEntityList = array_column($relatedEntityList, null, 'design_id');

        $relatedAllEntityMap = $this->entityModel
            ->where([
                'tenant_id' => $entity['tenant_id'],
                'project_id' => $entity['project_id'],
                'module_id' => $publishedModule['id'],
            ])
            ->field('id,name')
            ->select();
        $relatedAllEntityMap = array_column($relatedAllEntityMap, 'id', 'name');
        $cService = CommonService::instance('entity');

        foreach ($relatedEntityList as $designId => $relatedEntityItem) {
            $design = $relatedDesignMap[$designId];
            $relatedStr = explode(',', $design['related_str']);
            $currentRelatedIdList = [];
            foreach ($relatedStr as $relatedStrItem) {
                if (isset($relatedAllEntityMap[$relatedStrItem])) {
                    $currentRelatedIdList[] = $relatedAllEntityMap[$relatedStrItem];
                }
            }
            $currentModule = Module::$moduleDictData['module_index_by_id'][$relatedEntityItem['module_id']];

            $cService->setCurrentModuleCode($currentModule['code']);

            switch ($currentModule['code']) {
                case 'asset':
                    $updateField = 'asset_' . $publishedModule['code'];
                    break;
                case 'level':
                    $updateField = 'level_' . $publishedModule['code'];
                    break;
                default:
                    continue 2;
            }
            $updateData = [
                'id' => $relatedEntityItem['id'],
                $updateField => implode(',', $currentRelatedIdList),
            ];
            $cService->update($updateData, $currentModule['code'], false);

        }
        return count($relatedEntityList);
    }
}
